package ru.ifmo.network.skype;

import ru.ifmo.network.skype.audio.UserListHandler;
import ru.ifmo.network.skype.network.MessageHandler;
import ru.ifmo.network.skype.network.TCPClient;
import ru.ifmo.network.skype.network.data.UserAddress;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author avhaliullin
 */
public class UserListController implements MessageHandler {
    private static final long MAX_IDLE_INTERVAL_MS = 2500;

    private final Map<UserAddress, UserData> userDataMap = new HashMap<UserAddress, UserData>();
    private Set<UserListHandler> userListHandlers = new HashSet<UserListHandler>();
    private final Map<UserAddress, Long> lastAccessMap = new HashMap<UserAddress, Long>();
    private final Map<UserAddress, Long> groupedLastAccess = new HashMap<>();
    private final TCPClient tcpClient;
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    private final ConcurrentHashMap<UserAddress, Byte> gettingData = new ConcurrentHashMap<UserAddress, Byte>();


    public UserListController(final TCPClient tcpClient) {
        this.tcpClient = tcpClient;
        scheduler.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                long currentTime = System.currentTimeMillis();
                for (Iterator<Map.Entry<UserAddress, Long>> it = lastAccessMap.entrySet().iterator(); it.hasNext(); ) {
                    Map.Entry<UserAddress, Long> entry = it.next();
                    if (entry.getValue() + MAX_IDLE_INTERVAL_MS < currentTime) {
                        UserData userData = userDataMap.remove(entry.getKey());
                        it.remove();
                        if (userData != null) {
                            removeUser(userData);
                        }
                    }
                }
            }
        }, MAX_IDLE_INTERVAL_MS, MAX_IDLE_INTERVAL_MS, TimeUnit.MILLISECONDS);

        scheduler.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                for (UserAddress address : gettingData.keySet()) {
                    UserData newUserData = tcpClient.getUserData(address);
                    UserData oldUserData = userDataMap.remove(address);
                    if (oldUserData != null) {
                        if (!oldUserData.equals(newUserData)) {
                            removeUser(oldUserData);
                            newUser(newUserData);
                        }
                    } else {
                        newUser(newUserData);
                    }
                    userDataMap.put(address, newUserData);
                }
                gettingData.clear();
            }
        }, MAX_IDLE_INTERVAL_MS, MAX_IDLE_INTERVAL_MS, TimeUnit.MILLISECONDS);
    }

    private void newUser(UserAddress userAddress) {
        gettingData.put(userAddress, Byte.MAX_VALUE);
    }

    private void newUser(UserData user) {
        if (user != null) {
            for (UserListHandler handler : userListHandlers) {
                handler.newUser(user);
            }
        }
    }

    private void removeUser(UserData user) {
        for (UserListHandler handler : userListHandlers) {
            handler.removeUser(user);
        }
    }

    private void changeGroup(UserAddress userAddress, Long groupId) {
        UserData userData = userDataMap.get(userAddress);
        if (userData != null) {
            for (UserListHandler handler : userListHandlers) {
                handler.moveUser(userDataMap.get(userAddress), groupId);
            }
        }
    }

    public void addUserListHandler(UserListHandler userListHandler) {
        userListHandlers.add(userListHandler);
        for (UserData user : userDataMap.values()) {
            userListHandler.newUser(user);
        }
    }

    @Override
    public void handleHello(UserAddress userAddress) {
        lastAccessMap.put(userAddress, System.currentTimeMillis());

        UserData newUserData = tcpClient.getUserData(userAddress);
        UserData oldUserData = userDataMap.remove(userAddress);
        if (oldUserData != null) {
            if (!oldUserData.equals(newUserData)) {
                removeUser(oldUserData);
                newUser(newUserData);
            }
        } else {
            newUser(newUserData);
        }
        userDataMap.put(userAddress, newUserData);
        changeGroup(userAddress, null);
    }

    @Override
    public void handleGoodbye(UserAddress userAddress) {
        lastAccessMap.put(userAddress, System.currentTimeMillis());
        UserData user = userDataMap.remove(userAddress);
        if (user != null) {
            removeUser(user);
        }
        changeGroup(userAddress, null);
    }

    @Override
    public void handleAudio(UserAddress userAddress, long serialId, byte[] data) {
        Long time = lastAccessMap.put(userAddress, System.currentTimeMillis());
        Long groupTime = groupedLastAccess.get(userAddress);
        if (groupTime == null || time == null || time - groupTime > 300) {
            UserData oldUserData = userDataMap.get(userAddress);
            if (oldUserData == null) {
                newUser(userAddress);
            }
            changeGroup(userAddress, null);
        }
    }

    @Override
    public void handleGroup(UserAddress userAddress, long groupId, byte[] data) {
        long time = System.currentTimeMillis();
        lastAccessMap.put(userAddress, time);
        groupedLastAccess.put(userAddress, time);
        UserData oldUserData = userDataMap.get(userAddress);
        if (oldUserData == null) {
            newUser(userAddress);
        }
        changeGroup(userAddress, groupId);
    }
}
